1// libs: -lpulse
2#include <stdio.h>
3#include <stdlib.h>
4#include <errno.h>
5#include <string.h>
6#include <stdbool.h>
7#include <pulse/pulseaudio.h>
8
9#ifdef DEBUG
10 #define LOG(...) printf(__VA_ARGS__)
11#else
12 #define LOG(...)
13#endif
14
15typedef enum Method
16{
17 METHOD_PLAY,
18 METHOD_RECORD
19}Method;
20
21typedef struct UserData
22{
23 pa_context* context;
24 pa_stream* stream;
25 pa_mainloop_api* api;
26 FILE* fp;
27 Method method;
28}UserData;
29
30static void mainloop_quit(UserData* userdata, int ret);
31static void context_state_callback(pa_context* context, void* userdata) ;
32static void stream_write_callback(pa_stream* stream, size_t length, void* userdata);
33static void stream_read_callback(pa_stream* stream, size_t length, void* userdata);
34static void stream_drain_complete(pa_stream* stream, int success, void* userdata);
35static void context_drain_complete(pa_context* c, void* userdata);
36
37int main(int argc, char* argv[])
38{
39 // 参数检查
40 if(argc != 3)
41 {
42 printf("pademo [play|record] <sound-file>\n");
43 return EXIT_FAILURE;
44 }
45
46 // 创建一个线程,并在该线程中创建 mainloop
47 pa_mainloop* mainloop = pa_mainloop_new();
48 if(mainloop == NULL)
49 {
50 fprintf(stderr, "pa_threaded_mainloop_new failed.\n");
51 return EXIT_FAILURE;
52 }
53
54 // 获取API
55 pa_mainloop_api* api = pa_mainloop_get_api(mainloop);
56 if(api == NULL)
57 {
58 pa_mainloop_free(mainloop);
59 fprintf(stderr, "pa_threaded_mainloop_get_api failed.\n");
60 return EXIT_FAILURE;
61 }
62
63 // 创建上下文
64 pa_context* context = pa_context_new(api, "demo");
65 if(context == NULL)
66 {
67 pa_mainloop_free(mainloop);
68 fprintf(stderr, "pa_context_new failed.\n");
69 return EXIT_FAILURE;
70 }
71
72 UserData data;
73 if(strcmp(argv[1],"play") == 0)
74 {
75 data.fp = fopen(argv[2], "rb");
76 data.method = METHOD_PLAY;
77 }
78 else if(strcmp(argv[1], "record") == 0)
79 {
80 data.fp = fopen(argv[2], "wb");
81 data.method = METHOD_RECORD;
82 }
83 else
84 {
85 fprintf(stderr, "Unknown method %s\n", argv[1]);
86 // 释放
87 pa_context_unref(context);
88 pa_mainloop_free(mainloop);
89 return EXIT_FAILURE;
90 }
91
92 // 设置状态变化的回调函数,这是主入口
93 data.context = context;
94 data.api = api;
95 pa_context_set_state_callback(context, context_state_callback, (void*)(&data));
96
97 // 开始建立连接
98 if(pa_context_connect(context, NULL, PA_CONTEXT_NOFAIL, NULL) < 0)
99 {
100 pa_context_unref(context);
101 pa_mainloop_free(mainloop);
102 fprintf(stderr, "pa_context_connect failed.\n");
103 return EXIT_FAILURE;
104 }
105
106 // 运行mainloop
107 int ret = pa_mainloop_run(mainloop, NULL);
108
109 // 退出
110 pa_context_unref(context);
111 pa_mainloop_free(mainloop);
112 return ret;
113}
114
115// 退出主循环
116static void mainloop_quit(UserData* userdata, int ret)
117{
118 LOG("mainloop_quit\n");
119 userdata->api->quit(userdata->api, ret);
120}
121
122// 状态变化的回调函数
123static void context_state_callback(pa_context* context, void* userdata)
124{
125 UserData* data = (UserData*)(userdata);
126 pa_context_state_t state = pa_context_get_state(context);
127 switch (state)
128 {
129 case PA_CONTEXT_READY: // 上下文就绪
130 {
131 LOG("PA_CONTEXT_READY\n");
132
133 // 创建spec
134 pa_sample_spec sampleSpec;
135 sampleSpec.rate = 44100;
136 sampleSpec.format = PA_SAMPLE_S16LE;
137 sampleSpec.channels = 2;
138
139 // 创建channel map
140 pa_channel_map channelMap;
141 pa_channel_map_init_stereo(&channelMap);
142
143 // 创建stream
144 pa_stream* stream = pa_stream_new(context, "demo-stream", &sampleSpec, &channelMap);
145 data->stream = stream;
146
147 if(data->method == METHOD_PLAY) // 播放
148 {
149 pa_stream_connect_playback(stream, NULL, NULL, PA_STREAM_NOFLAGS, NULL, NULL);
150 pa_stream_set_write_callback(stream, stream_write_callback, userdata);
151 }
152 else if(data->method == METHOD_RECORD) // 录音
153 {
154 pa_stream_connect_record(stream, NULL, NULL, PA_STREAM_NOFLAGS);
155 pa_stream_set_read_callback(stream, stream_read_callback, userdata);
156 }
157 break;
158 }
159
160 case PA_CONTEXT_TERMINATED: // 结束
161 LOG("PA_CONTEXT_TERMINATED\n");
162 mainloop_quit(data, EXIT_SUCCESS);
163 break;
164
165 default:
166 LOG("context state %d\n", state);
167 }
168}
169
170// 播放的回调
171static void stream_write_callback(pa_stream* stream, size_t length, void* userdata)
172{
173 UserData* data = (UserData*)(userdata);
174 void* buffer;
175 while(true)
176 {
177 // 给buffer分配空间,不需要手动释放
178 pa_stream_begin_write(stream, &buffer, &length);
179
180 // 读取文件,写入stream
181 length = fread(buffer, 1, length, data->fp);
182 pa_stream_write(stream, buffer, length, NULL, 0, PA_SEEK_RELATIVE); // 会自动释放buffer
183 LOG("play %zu bytes\n", length);
184
185 // 文件读取完毕
186 if(feof(data->fp))
187 {
188 pa_stream_cancel_write(stream);
189 pa_stream_set_write_callback(stream, NULL, NULL); //清除回调
190 pa_operation* o = pa_stream_drain(stream, stream_drain_complete, data); // 设置播放完毕时的回调
191 if(o == NULL)
192 {
193 mainloop_quit(data, EXIT_FAILURE);
194 }
195 pa_operation_unref(o);
196 break;
197 }
198 }
199}
200
201// 录音的回调
202static void stream_read_callback(pa_stream* stream, size_t length, void* userdata)
203{
204 UserData* data = (UserData*)(userdata);
205 const void *buffer;
206 while(pa_stream_readable_size(stream) > 0)
207 {
208 pa_stream_peek(stream, &buffer, &length);
209 if(buffer == NULL || length <= 0)
210 {
211 continue;
212 }
213
214 fwrite(buffer, length, 1, data->fp);
215 fflush(data->fp);
216 LOG("record %zu bytes\n", length);
217 pa_stream_drop(stream);
218 }
219}
220
221// 播放完毕的回调
222static void stream_drain_complete(pa_stream* stream, int success, void* userdata)
223{
224 (void)(success);
225 LOG("stream_drain_complete\n");
226 UserData* data = (UserData*)(userdata);
227
228 // 释放stream
229 pa_stream_disconnect(stream);
230 pa_stream_unref(stream);
231 data->stream = NULL;
232
233 // 设置上下文结束的回调
234 pa_operation* o = pa_context_drain(data->context, context_drain_complete, NULL);
235 if (o == NULL)
236 {
237 pa_context_disconnect(data->context);
238 }
239 else
240 {
241 pa_operation_unref(o);
242 }
243}
244
245// 上下文结束的回调
246static void context_drain_complete(pa_context* context, void* userdata)
247{
248 (void)(userdata);
249 LOG("context_drain_complete\n");
250 pa_context_disconnect(context);
251}